import fs from '@ohos.file.fs';
import fileuri from '@ohos.file.fileuri';

/**
 * FileManager
 * 用于管理所有的文件操作.
 * 必须调用initClass才能操作.
 * 没有initClass使用本类方法,直接闪退.
 * dependencies:
 * ResourceManager
 */
import { FilePathGenerator } from './FilePathGenerator';
import { util } from '@kit.ArkTS';
import { resourceManager } from '@kit.LocalizationKit';
import { getResourceManager } from '../resource_manager/ResourceManager';
import { Context } from '@kit.AbilityKit';
import { BusinessError, zlib } from '@kit.BasicServicesKit';
import { picker } from '@kit.CoreFileKit';
import { StringUtil } from '../StringUtil';
import { FileBuffer, IBuffer } from '../../file_buffer/FileBuffer';
import { getLogger, Logger } from '../../logger/Logger';

const logger = new Logger('FileManager')

export class FileManager {
  static context: Context;

  static initClass(context: Context) {
    FileManager.context = context;
    let rootDir = context.filesDir;
    let cache = context.cacheDir;
    FilePathGenerator.initClass(rootDir, cache);
    FileManager.createDirSync(FilePathGenerator.mailsDir);
    FileManager.createDirSync(FilePathGenerator.cacheDir);
    FileManager.createDirSync(FilePathGenerator.dbDir);
    FileManager.createDirSync(FilePathGenerator.buffDir);
    FileManager.createDirSync(FilePathGenerator.logDir);
  }

  /**
   * 查询是否文件是否已存在本地沙盒中
   * @param context ability或application的上下文的能力，用于访问沙盒
   * @param mid 文件所属的邮件id（其实应该是uid+mid）
   * @param fileName 文件名
   * @returns 存在则返回路径，不存在返回null
   */
  static async isAttachmentExistForAccountMail(account: string, folderName: string, mid: string, fileTitle: string,
    fileExtension: string, isInline: boolean, index: number): Promise<string | null> {
    let filePath =
      await FilePathGenerator.getAttachmentPathForAccountMail(account, folderName, mid, isInline, fileTitle,
        fileExtension, index);
    let existed = await fs.access(filePath);
    if (existed) {
      return filePath;
    }
    return null;
  }

  static async isAttachmentExistForImportedMail(url: string, isInline: boolean, fileTitle: string,
    fileExtension: string, index: number) {
    let filePath =
      await FilePathGenerator.getAttachmentPathForImportedMail(url, isInline, fileTitle, fileExtension, index);
    let existed = fs.accessSync(filePath);
    if (existed) {
      return filePath;
    }
    return null;
  }

  /**
   * 沙盒是app专用的文件夹,用来存放程序生成的文件.
   * 判断沙盒中的文件或者目录是否存在.
   * file和dir都可以看做是一个item.
   * @param path
   * @returns
   */
  static isItemExist(path: string): boolean {
    return fs.accessSync(path);
  }


  /**
   * 返回父目录,本身就是顶层目录返回自身.
   * @param dir
   * @param seperator
   * @returns
   */
  static getParentDir(dir: string, seperator: string = "/"): string | null {
    let lastIndex = dir.lastIndexOf(seperator);
    return lastIndex <= 0 ? null : dir.substring(0, lastIndex);
  }

  /**
   * 创建dir的父级目录
   */
  static createParentDirSync(dir: string): void {
    let parentDir: string | null = FileManager.getParentDir(dir);
    if (parentDir) {
      if (fs.accessSync(parentDir)) {
        return;
      } else {
        FileManager.createParentDirSync(parentDir);
        fs.mkdirSync(parentDir);
      }
    } else {
      //没有父目录了,直接创建dir
      fs.mkdirSync(dir);
    }
  }

  /**
   * 创建dir,会从根依次创建目录,直至整个dir创建完毕.
   * @param dir
   */
  static createDirSync(dir: string): void {
    if (fs.accessSync(dir)) {
      return;
    }
    FileManager.createParentDirSync(dir)
    fs.mkdirSync(dir);
  }

  /**
   * 创建文件并写入初始内容.
   * @param filePath
   * @param initialContent
   */
  static createFileSync(filePath: string, initialContent?: string) {
    FileManager.createParentDirSync(filePath)
    // 新建并打开文件
    let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
    if (initialContent) {
      fs.writeSync(file.fd, initialContent);
    }
    // 关闭文件
    fs.closeSync(file);
  }

  /**
   * 将一个IBuffer保存到指定filePath
   * 如果IBuffer的路径本身就等于filePath,直接return.
   * @param data 文件内容或数据流
   * @param filePath 写入路径
   * @returns 指向filePath的FileBuffer
   */
  static async saveBufferToFile(data: IBuffer, filePath: string): Promise<FileBuffer> {
    if (data instanceof FileBuffer) {
      let fileBuffer = data as FileBuffer;
      if (fileBuffer.filePath == filePath) {
        return data;
      }
    }
    FileManager.createParentDirSync(filePath);
    const f = new FileBuffer(filePath);
    // for await (const chunk of data.readRaw()) {
    //   await f.feed(chunk);
    // }
    // await f.end();
    return f;
  }

  /**
   * 保存导入邮件的附件到沙盒下
   * @param context ability或application的上下文的能力，用于访问沙盒
   * @param mid 文件所属的邮件id（其实应该是uid+mid）
   * @param data 文件数据流
   * @param fileName 文件名
   * @param inline 是否是内联附件
   * @returns 文件保存在沙盒中的路径
   */
  static async saveImportedMailAttachmentToFile(
    url: string,
    data: IBuffer,
    fileTitle: string,
    fileExtension: string,
    isInline: boolean,
    index: number
  ): Promise<string> {
    let filePath =
      await FilePathGenerator.getAttachmentPathForImportedMail(url, isInline, fileTitle, fileExtension, index);
    FileManager.createParentDirSync(filePath);
    try {
      await FileManager.saveBufferToFile(data, filePath);
    } catch (e: unknown) {
      getLogger('save-file').error("failed", e);
    }
    return filePath;
  }

  /**
   * 将沙盒路径转换为URI
   * @param filePath 沙盒路径
   * @returns URI
   */
  static uri(filePath: string): string {
    return fileuri.getUriFromPath(filePath);
  }


  /**
   * 一次性从二进制文件读取字节流
   * @param filePath 文件路径
   * @param size 指定size则读取指定大小,没有指定size读取全部数据.
   * @returns
   * @returns 文件数据流
   */
  static readBytesFromSandboxFileSync(filePath: string, size?: number): ArrayBuffer {
    if (!size) {
      let stat = fs.statSync(filePath) as fs.Stat;
      size = stat.size;
    }
    let buffer = new ArrayBuffer(size);
    let readFile = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
    fs.readSync(readFile.fd, buffer, { offset: 0, length: size });
    fs.close(readFile)
    return buffer
  }

  /**
   * 外部需要确保filePath文件存在.
   * @param filePath
   * @param size
   * @returns
   */
  static readTextFromSandboxFileSync(filePath: string, size?: number): string {
    if (!size) {
      return fs.readTextSync(filePath);
    } else {
      let buffer: ArrayBuffer = FileManager.readBytesFromSandboxFileSync(filePath, size);
      let u8a = new Uint8Array(buffer);
      let text = StringUtil.uint8ArrayToString(u8a)
      return text;
    }
  }

  /**
   * 判断rawFile是否存在.
   * RawFile是和程序放在一起的文件,可以看作是程序的一部分.
   * @param rawFileName
   * @returns
   */
  static isRawFileExist(rawFileName: string): boolean {
    try {
      getResourceManager().getRawFdSync(rawFileName);
      getResourceManager().closeRawFdSync(rawFileName);
      return true;
    } catch (e) {
      return false;
    }
  }

  static readTextFromRawFileSync(fileName: string): string {
    let byteArray: Uint8Array = FileManager.readBytesFromRawFile(fileName);
    let content = FileManager.uint8ArrayToString(byteArray);
    return content;
  }

  static readBytesFromRawFile(fileName: string): Uint8Array {
    let rm: resourceManager.ResourceManager = getResourceManager();
    let byteArray = rm.getRawFileContentSync(fileName)
    return byteArray;
  }

  static readBytesFromUri(uri: string) {
    //TODO
  }

  static readTextFromUri(uri: string) {
    //TODO
  }


  /**
   * 写入文件到file.
   * 写入之前,需要确保文件目录全部已经被创建.
   * 只写的方式.
   * @param content
   * @param filePath,文件名
   */
  static writeToFile(content: string | ArrayBuffer, filePath: string): string {
    FileManager.createParentDirSync(filePath);
    let file = fs.openSync(filePath, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
    fs.writeSync(file.fd, content);
    fs.closeSync(file.fd);
    return filePath;
  }


  /**
   * 将系统对话框返回uri数组拷贝到cache目录下,并返回filePath数组.
   * @param context
   * @param arr
   * @returns
   */
  static copyFilesFromUriToSandbox(arr: Array<string>): string[] {
    let caches = new Array<string>(arr.length);
    for (let i = 0; i < arr.length; i++) {
      caches[i] = FileManager.copyFileFromUriToSandbox(arr[i])
    }
    return caches;
  }

  static getFileInfo(filePath: string): fs.Stat {
    let fileStat = fs.statSync(filePath);
    return fileStat
  }

  static getFileNameFromPath(filePath: string): string {
    return filePath.substring(filePath.lastIndexOf("/") + 1);
  }

  static getFolderFullName(name: string, parentName?: string): string {
    return parentName ? `${parentName}/${name}` : name;
  }

  static getPathLastComponent(filePath: string): string {
    return filePath.substring(filePath.lastIndexOf("/") + 1);
  }

  /**
   * 从文件名中取文件标题
   * xxx.aa.bb
   *
   * @param fileName
   * @returns
   */
  static getFileTitleFromName(fileName: string): string {
    let index = fileName.indexOf(".");
    return index == -1 ? fileName : fileName.substring(0, index);
  }

  /**
   * 从文件名中取文件标题
   * xxx.aa.bb
   * 应该取第一个. 有的后缀有多个点.
   * xxx.tar.gz
   *
   * @param fileName
   * @returns
   */
  static getFileExtFromName(fileName: string): string | null {
    let index = fileName.indexOf(".");
    return index == -1 ? null : fileName.substring(index + 1);
  }


  /**
   * 复制文件从沙盒到沙盒/系统目录uri
   * src ==> dst
   * @param context
   * @param srcFilePath
   * @param dstFilePath
   * @param createDir 是否创建目标目录的文件夹，默认true
   * @returns
   */
  static copyFileSync(srcFilePath: string, dstFilePath: string, createDir: boolean = true): void {
    if (createDir) {
      FileManager.createParentDirSync(dstFilePath);
    }
    try {
      let buffer = new ArrayBuffer(4096);
      let readFile = fs.openSync(srcFilePath, fs.OpenMode.READ_ONLY);
      let readLen = fs.readSync(readFile.fd, buffer, { offset: 0, length: 4096 });

      let offset = 0;

      let writeFile = fs.openSync(dstFilePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      while (readLen > 0) {
        fs.writeSync(writeFile.fd, buffer, { offset: offset, length: readLen });
        offset += readLen;
        readLen = fs.readSync(readFile.fd, buffer, { offset: offset, length: 4096 });
      }
      fs.closeSync(readFile);
      fs.closeSync(writeFile);
    } catch (e) {
      logger.warn(JSON.stringify(e))
    }
  }


  /**
   * 将文件从系统选择器返回的uri,拷贝到app内部的目录`$fidrDir/MailCache`下面并返回其路径
   * @param context
   * @param uriPath
   * 文件选择器返回的uri:
   * eg:
   * file://cn.coremail.hwmail/data/storage/el2/base/haps/tablet/files/1.png
   * @Param fileName
   *  拷贝后的文件名,
   * @returns
   */
  static copyFileFromUriToSandbox(uriPath: string, fileName?: string): string {
    let dir = FilePathGenerator.cacheDir!;
    FileManager.createDirSync(dir);

    if (!fileName) {
      //如果外部没有提供文件名,取文件路径的最后面的文件名作为文件名.
      fileName = decodeURIComponent(FileManager.getFileNameFromPath(uriPath))
    }
    let filePath = `${dir}/` + fileName;
    try {
      // let fileStat = await fs.stat(uriPath);
      // let buffer = new ArrayBuffer(fileStat.size > 4096 ? 4096 : fileStat.size);
      let buffer = new ArrayBuffer(4096);
      let readFile = fs.openSync(uriPath, fs.OpenMode.READ_ONLY);
      let readLen = fs.readSync(readFile.fd, buffer, { offset: 0, length: 4096 });

      let offset = 0;

      let writeFile = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      while (readLen > 0) {
        fs.writeSync(writeFile.fd, buffer, { offset: offset, length: readLen });
        offset += readLen;
        readLen = fs.readSync(readFile.fd, buffer, { offset: offset, length: 4096 });
      }
      fs.closeSync(readFile);
      fs.closeSync(writeFile);
      return filePath;
    } catch (e : unknown) {
      logger.warn(e as string)
    }
    return "";
  }


  static uint8ArrayToString(fileData: Uint8Array): string {
    let dataString: string = "";
    let decoder = util.TextDecoder.create('utf-8');
    dataString = decoder.decodeWithStream(fileData);
    return dataString
  }

  static getContent(filePath: string): string {
    return fs.readTextSync(filePath);
  }


  /**
   * 保存本地文件到一个uri
   * @param uriPath 已经存在的uriPath,通过文件选择器创建.
   * @param filePath
   * @returns
   */
  static copyFileToUri(filePath: string, uriPath: string): string {
    if (fs.accessSync(filePath)) {
      let buffer = new ArrayBuffer(4096);
      let readFile = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
      let readLen = fs.readSync(readFile.fd, buffer, { offset: 0, length: 4096 });
      let offset = 0;
      let writeFile = fs.openSync(uriPath, fs.OpenMode.READ_WRITE);
      while (readLen > 0) {
        fs.writeSync(writeFile.fd, buffer, { offset: offset, length: readLen });
        offset += readLen;
        readLen = fs.readSync(readFile.fd, buffer, { offset: offset, length: 4096 });
      }
      fs.closeSync(readFile);
      fs.closeSync(writeFile);
    }
    return filePath;
  }


  /**
   * 追加内容到file,文件不存在则新建
   * 写入之前,需要确保文件目录全部已经被创建.
   * 只写的方式.
   * @param content
   * @param fileName,文件名
   */
  static writeToFileSync(content: string, filePath: string): string {
    FileManager.createDirSync(filePath);
    let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
    fs.writeSync(file.fd, content);
    fs.closeSync(file.fd);
    return filePath;
  }

  /**
   * 返回一个文件夹的所有子item的itemPath.
   * @param dirPath
   * @returns
   */
  static fileListInDir(dirPath: string): string[] {
    if (fs.accessSync(dirPath)) {
      let list = fs.listFileSync(dirPath);
      let itemPaths = list.map((value, index) => {
        let itemPath = `${dirPath}/${value}`
        return itemPath;
      });
      return itemPaths;
    } else {
      return [];
    }
  }

  static isItemDir(path: string): boolean {
    return fs.statSync(path).isDirectory();
  }

  /**
   * 递归删除文件夹苏有的内容.
   * @param dirPath
   */
  static removeDirR(dirPath: string): void {
    if (FileManager.isItemExist(dirPath) && FileManager.isItemDir(dirPath)) {
      //可以删除有内容的文件夹吗?
      let subItems = FileManager.fileListInDir(dirPath);
      for (const item of subItems) {
        if (FileManager.isItemDir(item)) {
          this.removeDirR(item);
        } else {
          this.removeFile(item);
        }
      }
      fs.rmdirSync(dirPath);
    }
  }

  /**
   * 删除单个文件
   * @param filePath
   */
  static removeFile(filePath: string): void {
    fs.unlinkSync(filePath)
  }


  /**
   * 保存文件到系统相册
   * @returns
   */
  static async copyFileFromSandboxToSystemPhoto(): Promise<void> {
    try {
      let saveOptions: picker.PhotoSaveOptions = new picker.PhotoSaveOptions();
      saveOptions.newFileNames = ['PhotoViewPicker01.jpg', 'PhotoViewPicker01.mp4'];
      let photoPicker = new picker.PhotoViewPicker();
      let ret: Array<string> = await photoPicker.save(saveOptions);
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      logger.error('PhotoViewPicker failed with err: ' + JSON.stringify(err));
    }
  }

  /**
   * 保存文件到系统文档
   * @param filePath
   * @returns
   */
  static async copyFileFromSandboxToSystemDoc(filePath: string): Promise<void> {
    try {
      let saveOptions: picker.DocumentSaveOptions = new picker.DocumentSaveOptions();
      // saveOptions.maxSelectNumber = 1;
      let fileName = FileManager.getFileNameFromPath(filePath);
      saveOptions.newFileNames = [fileName];
      let docPicker: picker.DocumentViewPicker = new picker.DocumentViewPicker();
      let result = await docPicker.save(saveOptions);
      if (result.length == 1) {
        let cpFilePath = result[0];
        FileManager.copyFileSync(filePath, cpFilePath, false);
      } else {
        logger.log("save failed");
      }
    } catch (error) {
      let err: BusinessError = error as BusinessError;
      logger.error('PhotoViewPicker failed with err: ' + JSON.stringify(err));
    }
  }

  /**
   * 文件夹压缩并存入到系统目录下
   * @param dirPath 文件夹路径
   * @param zipName 压缩包名称
   */
  static async dirCompressAndSaveToSystem(dirPath: string, zipName: string) {
    const zipPath = `${dirPath}/${zipName}.zip`
    try {
      fs.unlink(zipPath)
    } catch (e) {
      throw e
    }
    const options: zlib.Options = {
      level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION,
      memLevel: zlib.MemLevel.MEM_LEVEL_DEFAULT,
      strategy: zlib.CompressStrategy.COMPRESS_STRATEGY_DEFAULT_STRATEGY
    }
    const promise = new Promise<void>((resolve, reject) => {
      zlib.compressFile(dirPath, zipPath, options, async (err) => {
        if (err) {
          reject(err)
        } else {
          await FileManager.copyFileFromSandboxToSystemDoc(zipPath)
          fs.unlink(zipPath)
          resolve()
        }
      })
    })
    await promise
  }
}

